package rmioverlay.server;

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import rmioverlay.client.ClientAppInterface;
import rmioverlay.lib.users.*;

import java.util.HashMap;
import java.util.Iterator;

public abstract class ServerApp extends UnicastRemoteObject implements ServerAppInterface {
	private static final long serialVersionUID = 1L;
	
	/**
	 * The registered client list.
	 */
	protected HashMap<ClientAppInterface<ServerAppInterface>, User> clientList;
	
	public ServerApp()
			throws RemoteException {
		super();
		this.clientList = new HashMap<ClientAppInterface<ServerAppInterface>, User>();
	}
	
	/**
	 * Registers a recently connected client in client list
	 * @param pClient The client to register
	 * @throws RemoteException if a network issue occurs
	 */
	@SuppressWarnings("rawtypes")
	public synchronized void addClient(ClientAppInterface pClient)
			throws RemoteException {
		this.addClient(pClient, null);
	}
	
	/**
	 * Registers a recently connected client in client list with a specific User
	 * @param pClient The client to register
	 * @throws RemoteException if a network issue occurs
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public synchronized void addClient(ClientAppInterface pClient, User user)
			throws RemoteException {
		this.clientList.put(pClient, user);
	}

	/**
	 * Removes a registered client
	 * @param pClient The client to remove
	 * @throws RemoteException if a network issue occurs
	 */
	@SuppressWarnings("rawtypes")
	public synchronized void removeClient(ClientAppInterface pClient)
			throws RemoteException {
		this.clientList.remove(pClient);
	}

	/**
	 * Sends a message to all connected clients
	 * @param data the data to send
	 * @throws RemoteException if a network issue occurs
	 */
	public synchronized <T>void sendAll(T data)
			throws RemoteException {			
		
		Iterator<ClientAppInterface<ServerAppInterface>> i = this.clientList.keySet().iterator();
		ClientAppInterface<ServerAppInterface> c;
		
		while(i.hasNext()) {
			c = i.next();
			try {
				c.<T>receive(data);
			} catch(RemoteException e) {
				this.removeClient(c);
			}
		}
	}

	/**
	 * Sends a byte array to all connected clients
	 * @param pData the data to send
	 * @param pFilename the original file name
	 * @throws RemoteException if a network issue occurs
	 */
	@SuppressWarnings("rawtypes")
	public synchronized void sendAll(byte[] pData, String pFilename)
			throws RemoteException {

		Iterator<ClientAppInterface<ServerAppInterface>> i = this.clientList.keySet().iterator();
		ClientAppInterface c;
		
		while(i.hasNext()) {
			c = i.next();
			try {
				c.receive(pData, pFilename);
			} catch(RemoteException e) {
				this.removeClient(c);
			}
		}
	}

	/**
	 * Sends data to a registered client
	 * @param <T> the type of data which will be sent
	 * @param pLogin the login of recipient user
	 * @param data the data to send
	 * @throws RemoteException if a network issue occurs
	 */
	public synchronized <T>void send(String pLogin, T data)
			throws RemoteException {			
		ClientAppInterface<ServerAppInterface> c = null;
		Iterator<ClientAppInterface<ServerAppInterface>> i = this.clientList.keySet().iterator();
		while(i.hasNext()) {
			c = i.next();
			if (pLogin.equals(clientList.get(c).getLogin())){
				break;
			}
		}
		if (c != null) {
			try {
				c.<T>receive(data);
			} catch(RemoteException e) {
				this.removeClient(c);
			}
		}
	}

	/**
	 * Sends a byte array to a connected client
	 * @param pLogin the login of recipient user
	 * @param pData the data to send
	 * @param pFilename the original file name
	 * @throws RemoteException if a network issue occurs
	 */
	public synchronized void send(String pLogin, byte[] pData, String pFilename)
			throws RemoteException {
		ClientAppInterface<ServerAppInterface> c = null;
		Iterator<ClientAppInterface<ServerAppInterface>> i = this.clientList.keySet().iterator();
		while(i.hasNext()) {
			c = i.next();
			if (pLogin.equals(clientList.get(c).getLogin())){
				break;
			}
		}
		if (c != null) {
			try {
				c.receive(pData, pFilename);
			} catch(RemoteException e) {
				this.removeClient(c);
			}
		}
	}

	/**
	 * Logs a user in
	 * @param pClient the ClientApp reference
	 * @param pLogin the typed login
	 * @param pPass the typed password
	 * @return whether user has been logged in successfully or not
	 * @throws RemoteException if a network issue occurs
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public synchronized boolean login(ClientAppInterface pClient, String pLogin, String pPass)
			throws RemoteException {
		
		User u = UserList.getUser(pLogin);
		if (u != null && u.login(pPass)) {
			this.clientList.put(pClient, u);
			return true;
		}
		return false;
	}
}